/*
 *  File: json.js
 *  This file defines various utilities for dealing with JSON.  It is
 *  automatically included when movenetworks.js is included.
 *
 *  License:
 *  Public Domain, modifications by Move Networks (see Changes below).
 *
 *  Reference:
 *  See http://www.JSON.org/js.html for original file.
 *
 *  Changes:
 *  dbrown - Modified to allow single-quoted strings in parsing a JSON
 *           string due to a bug in AsyncLoadQMX returning such strings.
 *  dbrown - Function documentation has been moved to the top of each function.
 */

/*jslint evil: true */
/*extern JSON */

if (!this.JSON)
{

    /**
     * Class: JSON
     *
     * This object defines various utility methods useful in dealing with JSON.
     */
    JSON = function ()
    {

        function f(n)
        {    // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function ()
        {
            // Eventually, this method will be based on the date.toISOString method.
            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };


        var m =
        {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };
        /**
         * Method: stringify
         *
         * This method produces a JSON text from a JavaScript value.
         * There are three possible ways to stringify an object, depending
         * on the optional whitelist parameter.
         *
         * If an object has a toJSON method, then the toJSON() method will be
         * called. The value returned from the toJSON method will be
         * stringified.
         *
         * Otherwise, if the optional whitelist parameter is an array, then
         * the elements of the array will be used to select members of the
         * object for stringification.
         *
         * Otherwise, if there is no whitelist parameter, then all of the
         * members of the object will be stringified.
         *
         * Values that do not have JSON representaions, such as undefined or
         * functions, will not be serialized. Such values in objects will be
         * dropped, in arrays will be replaced with null. JSON.stringify()
         * returns undefined. Dates will be stringified as quoted ISO dates.
         *
         * Parameters:
         * value - Any JavaScript value, usually an object or array.
         * whitelist - An optional that determines how object values are
         *             stringified.
         *
         * Example:
         *
         * var text = JSON.stringify(['e', {pluribus, 'unum'}]);
         * // text is '["e",{"pluribus":"unum"}]'
         */
        function stringify(value, whitelist)
        {
            var a,          // The array holding the partial texts.
                i,          // The loop counter.
                k,          // The member key.
                l,          // Length.
                v;          // The member value.

            switch (typeof value)
            {
            case 'string':

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe sequences.

                return /["\\\x00-\x1f]/.test(value) ?
                    '"' + value.replace(/[\x00-\x1f\\"]/g, function (a)
                    {
                        var c = m[a];
                        if (c) { return c; }
                        c = a.charCodeAt();
                        return '\\u00' + Math.floor(c / 16).toString(16) +
                                                   (c % 16).toString(16);
                    }) + '"' :
                    '"' + value + '"';

            case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
                return String(value);

            case 'null':
                return 'null';

            case 'object':

// Due to a specification blunder in ECMAScript,
// typeof null is 'object', so watch out for that case.

                if (!value) { return 'null'; }

// If the object has a toJSON method, call it, and stringify the result.

                if (typeof value.toJSON === 'function')
                {
                    return stringify(value.toJSON());
                }
                a = [];
                if (value.constructor === Array)
                {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                    l = value.length;
                    for (i = 0; i < l; i += 1)
                    {
                        a.push(stringify(value[i], whitelist) || 'null');
                    }

// Join all of the elements together and wrap them in brackets.

                    return '[' + a.join(',') + ']';
                }
                if (whitelist)
                {

// If a whitelist (array of keys) is provided, use it to select the components
// of the object.

                    l = whitelist.length;
                    for (i = 0; i < l; i += 1)
                    {
                        k = whitelist[i];
                        if (typeof k === 'string')
                        {
                            v = stringify(value[k], whitelist);
                            if (v) { a.push(stringify(k) + ':' + v); }
                        }
                    }
                }
                else
                {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value)
                    {
                        if (typeof k === 'string')
                        {
                            v = stringify(value[k], whitelist);
                            if (v) { a.push(stringify(k) + ':' + v); }
                        }
                    }
                }

// Join all of the member texts together and wrap them in braces.

                return '{' + a.join(',') + '}';
            }
        }

        return {
            stringify: stringify,

            /**
             * Method: parse
             *
             * This method parses a JSON text to produce an object or
             * array. It can throw a SyntaxError exception.  The optional
             * filter parameter is a function that can filter and transform
             * the results. It receives each of the keys and values, and its
             * return value is used instead of the original value. If it
             * returns what it received, then structure is not modified. If it
             * returns undefined then the member is deleted.
             *
             * Parameters:
             *
             * text - The text to parse.
             * filter - Optional function that can filter and transform the
             *          results.
             *
             * Example:
             *
             * // Parse the text. If a key contains the string 'date' then
             * // convert the value to a date.
             *
             * myData = JSON.parse(text, function (key, value) {
             *     return key.indexOf('date') >= 0 ? new Date(value) : value;
             * });
             */
            parse: function (text, filter)
            {
                var j;

                function walk(k, v)
                {
                    var i, n;
                    if (v && typeof v === 'object')
                    {
                        for (i in v)
                        {
                            if (Object.prototype.hasOwnProperty.apply(v, [i]))
                            {
                                n = walk(i, v[i]);
                                if (n !== undefined) { v[i] = n; }
                            }
                        }
                    }
                    return filter(k, v);
                }


// Parsing happens in three stages. In the first stage, we run the text against
// regular expressions that look for non-JSON pattern. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we want to reject all
// unexpected forms.

// We split the first stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace all backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.test(text.replace(/\\./g, '@').
replace(/['"][^"\\\n\r]*["']|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, '')))
                {

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

// In the optional third stage, we recursively walk the new structure, passing
// each name/value pair to a filter function for possible transformation.

                    return typeof filter === 'function' ? walk('', j) : j;
                }

// If the text is not JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('parseJSON');
            }
        };
    }();
}
